Skip to content

image-20251124114629342

image-20251124114733572

A: 在这块代码中,为什么ActivityController要引用的是service 目录下的几个 service 接口对象了,还不是注入 port 目录下的,对外统一暴露的抽奖接口类。这样是不是没做到防腐层,感觉这样像是 mvc 的写法,一个接口一个实现类,接口还没加防腐层。你仔细看下我关于活动的这块代码,还是说是因为按照活动的不同业务来划分的了

Q:

问题分析

您提出了一个很关键的架构问题:为什么 ActivityController 要直接引用 service 目录下的多个 service 接口(IActivityApplicationService、IActivityMemberApplicationService、IActivityQueryApplicationService),而不是引用 port 目录下统一的对外暴露接口?

当前架构现状

查看代码后,我发现:

1. port 目录的实际用途

java
ActivityNotificationPort.java Lines 50-70
  
  public interface ActivityNotificationPort {  
  // 这是一个输出端口(Output Port)  
  // 定义了Domain层需要调用外部系统的契约  
  // 例如:发送通知、消息推送等  
  void notifyActivityCreated(...); 
  void notifyActivityCancelled(...);
}

2. service 目录的实际用途

java
ActivityController.java Lines 6-62
// 这些是**应用服务接口**(Application Service Interface)
// 是Domain层对外提供的业务能力
@Resource
private IActivityApplicationService activityApplicationService;

@Resourceprivate 
private IActivityMemberApplicationService activityMemberApplicationService;

@Resourceprivate 
private IActivityQueryApplicationService activityQueryApplicationService;

架构设计的合理性

✅ 您的代码是合理的,原因如下:

1. Port 与 Service 的不同职责

Port(端口接口):

  • 方向:Domain → 外部系统(输出端口)

  • 用途:Domain 层调用外部服务(如消息推送、第三方API)

  • 实现位置:Infrastructure 层

  • 举例:ActivityNotificationPort(Domain 需要发送通知)

ApplicationService(应用服务):

  • 方向:外部 → Domain(输入端口)

  • 用途:Trigger 层调用 Domain 层的业务能力

  • 实现位置:Domain 层(或 App 层)

  • 举例:IActivityApplicationService(外部调用活动业务)

2. 这是标准的六边形架构模式

┌─────────────────────────────────────────┐

│     Trigger Layer (外部适配器)    				 │

│  ActivityController      						    │

│     ↓ 调用            								   │

│  IActivityApplicationService (输入端口)  │

└─────────────────────────────────────────┘



┌─────────────────────────────────────────┐

│     Domain Layer (业务核心)      				│

│  ActivityApplicationService     			 │

│  ActivityAggregate (业务逻辑)    			  │

│     ↓ 调用             								 │

│  ActivityNotificationPort (输出端口)  	│

└─────────────────────────────────────────┘



┌─────────────────────────────────────────┐

│  Infrastructure Layer (基础设施)   			 │

│  ActivityNotificationAdapter   				  │

└─────────────────────────────────────────┘

3. 按业务能力划分 Service 是合理的

您按照 CQRS 和业务职责划分了三个 Service:

  • IActivityApplicationService - 活动命令操作(创建、发布、取消等)

  • IActivityQueryApplicationService - 活动查询操作(读操作)

  • IActivityMemberApplicationService - 成员管理操作

这种划分优于一个大而全的接口,因为:

  • ✅ 符合接口隔离原则(ISP)

  • ✅ 符合单一职责原则(SRP)

  • ✅ 支持CQRS读写分离

  • ✅ 便于独立扩展和测试

与 MVC 的区别

您担心这样"像 MVC 的写法",但实际上有本质区别:

对比点MVC模式您的DDD架构
Service定位贫血模型,Service包含所有业务逻辑充血模型,Service是应用服务编排
业务逻辑位置在Service层在 Domain 的 Aggregate 和 Entity 中
接口作用简单的接口+实现六边形架构的端口(Port)
防腐层通常没有Port 是防腐层,隔离外部依赖

您的代码中:

java
ActivityController.java Lines 84-97

// Controller 只做协议转换

Location location = Location.builder()...

// 调用应用服务(不包含业务逻辑)

String activityId = activityApplicationService.createActivity(...);

真正的业务逻辑在:

java
// ActivityAggregate 或 ActivityEntity(充血模型)

public class ActivityAggregate {

  public void publish() {

     // 业务规则:只有草稿状态才能发布

     if (this.status != ActivityStatus.DRAFT) {

       throw new AppException("只有草稿状态的活动才能发布");

     }

     // ... 业务逻辑

  }

}

建议改进(可选)

如果您希望进一步优化,可以考虑:

方案1:引入 Facade 模式(统一入口)

如果 Controller 需要调用的服务接口太多,可以引入一个 Facade:

java
// 可选:ActivityFacade 作为统一入口

public interface IActivityFacade {

  // 组合多个 ApplicationService

  IActivityApplicationService commandService();

  IActivityQueryApplicationService queryService();

  IActivityMemberApplicationService memberService();

}

但这不是必须的,当前的设计已经很好了。

方案2:进一步使用 Use Case 模式

java
// 每个业务场景一个 UseCase
public interface CreateActivityUseCase {  String execute(CreateActivityCommand command);}

但这会增加复杂度,对中小型项目不一定合适。

总结

✅ 您的架构设计是正确的:

  1. port 目录存放输出端口(Domain调用外部)

  2. service 目录存放应用服务接口(外部调用Domain)

  3. 按业务能力划分多个 Service 接口是良好实践

  4. 这不是 MVC 模式,而是标准的DDD + 六边形架构

    ✅ 防腐层已经存在:

  • ActivityNotificationPort 就是防腐层,隔离外部通知系统

  • IActivityRepository 是防腐层,隔离数据库

  • 其他 Port 接口也起到防腐作用

您的理解和代码实现都是符合 DDD 六边形架构规范的!按业务划分多个 Service 接口是正确的架构决策。

Powered by VitePress